package com.wl.spring.mvc.web;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import static com.sun.tools.javac.jvm.ByteCodes.ret;


public class MyDispatchServlet extends HttpServlet {
    AnnotationConfigApplicationContext context;

    public MyDispatchServlet(AnnotationConfigApplicationContext context) {
    }


    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        System.out.println("req.getServletPath() = " + req.getServletPath());

        //根据请求地址从IOC容器中查找控制器方法
        Handler handler = searchHandlerhandlerMapping(req.getServletPath());
        System.out.println("handler = " + handler);

        //执行控制器方法
        //判断是否为空
        if(handler != null) {
            //判断是不是静态资源
            if(isStaticRescource(req)){
                //是静态资源
                processStaticResource(req,resp);
            }else {
                // 为空且不是静态资源   404
                String webpath = req.getServletPath();
                String filepath = req.getServletContext().getRealPath(webpath);
                System.out.println("404，该资源不存在");
            }
        }else {
            //不为空  动态资源  执行控制器方法
            Object ret = executeHandler(handler,req,resp);

            //处理控制器方法的返回结果 json, 页面跳转
            boolean isRestController = handler.getController().getClass().getAnnotation(RestController.class) != null;
            boolean isResponseBody = handler.getMethod().getAnnotation(ResponseBody.class) != null;
            if(isRestController && ret instanceof ModelAndView == false || isResponseBody){
                ObjectMapper objectMapper = new ObjectMapper();
                String json = objectMapper.writeValueAsString(ret);
                //通过响应对象将内容输出，设置输出类型
                resp.setContentType("application/json;charset=utf-8");
                //获取输出流
                resp.getWriter().println(json);
            }else {
                //视图 : 如果返回的是String类型 表示 视图名 ，  如果是ModelAndView => 视图模型、试图对象 如果要提取视图名，那就是ModelAndView.getViewName
                //意味着我们要对结果做判断
                String viewName = null;
                if(ret instanceof String){
                    viewName = (String) ret;
                } else if (ret instanceof ModelAndView) {
                    viewName = ((ModelAndView) ret).getViewName();
                }
                //接下来进行页面跳转
                //跳转页面方式：1、请求转发  地址栏不变 forward(如果没有带前缀或者前缀是forward都是请求转发)
                //           2、响应重定向   地址栏变化 redirect
                boolean isForward = false;
                if(viewName.startsWith("forward:")){
                    //如果前缀是这，那么是请求转发
                    isForward = true;
                    //如果包含前缀，那么要将前缀给去掉
                    viewName = viewName.substring("forward:".length());
                } else if (viewName.startsWith("redirect:")) {
                    //如果前缀是redirect,那么是响应重定向
                    viewName = viewName.substring("redirect:".length());
                }else {
                    //如果两个都没有，那么就是默认的forward.即请求转发
                    isForward = true;
                }
                if(isForward){
                    //如果是请求转发，下面这样跳转。
                    RequestDispatcher dispatcher = req.getRequestDispatcher(viewName);
                    //转发器把请求响应传过去
                    dispatcher.forward(req, resp);
                }else {
                    resp.sendRedirect(viewName);
                }
            }

        }
    }

    private void processStaticResource(HttpServletRequest req, HttpServletResponse resp) {
        //将文件发送给浏览器
        String webpath = req.getServletPath();
        String filepath = req.getServletContext().getRealPath(webpath);
            //try括号里面定义的变量没有语法报错是因为 try一般是用来访问获取资源，一般需要finally来关闭资源
            //但是由于这种写法太多，就做了简化，这个流可以直接关闭，不需要再写finally来关闭
        //括号里面的输出流是服务磁盘，读完就关闭了
        try(FileInputStream fis = new FileInputStream(filepath)){
            //servlet输出流 该输出流的目的地是浏览器。该输出流仅仅只是获取，不能由我们来关闭
            ServletOutputStream out = resp.getOutputStream();
            byte[] bytes = new byte[1024];
            int count;
                   //赋值语句
            while((count = fis.read(bytes)) > -1){
                //读到数字 输出
                out.write(bytes, 0, count);

            }
        } catch (Exception e) {
            throw new RuntimeException("读取资源问价错误！",e);
        }
    }

    private Object executeHandler(Handler handler,
                                  HttpServletRequest req,
                                  HttpServletResponse resp) {
        // 获取控制器方法的参数列表

        //获取参数数组
        Parameter[] parameters = handler.method.getParameters();
        List<Object> paramsValues = new ArrayList<>();

        LocalVariableTableParameterNameDiscoverer lv = new LocalVariableTableParameterNameDiscoverer();
        //将方法参数传入进来，得到一个参数数组
        String[] parameterNames = lv.getParameterNames(handler.method);

        int i = 0; //作为下标，方便寻找
        //对参数进行遍历
        for (Parameter parameter : parameters) {
            //String name = parameter.getName();
            String name = parameterNames[i++];
            String svalue = req.getParameter(name);
            //参数类型
            Class<?> type = parameter.getType();
            Object ovalue = svalue;
            if(type.isAssignableFrom(Integer.class) || type.isAssignableFrom(int.class)){
                ovalue = Integer.valueOf(svalue);
            }else if (type.isAssignableFrom(Boolean.class) || type.isAssignableFrom(boolean.class)){
                ovalue = Boolean.valueOf(svalue);
            } else if (type.isAssignableFrom(String.class)) {
                //字符串不用处理
            } else if (type.isAssignableFrom(HttpServletRequest.class)) {
                ovalue = req;
            } else if (type.isAssignableFrom(HttpServletResponse.class)) {
                ovalue = resp;
            } else if (type.isAssignableFrom(HttpSession.class)) {
                ovalue = req.getSession();
            } else if (type.isAssignableFrom(ModelAndView.class)) {
                ovalue = new ModelAndView();
                //TODO 其他类型都处理完成之后，剩下就是实体类
            }else {
                //实体类对象 User
                //使用反射将请求参数设置到对象属性中
                try {
                    Object o = parameter.getType().newInstance();
                    for (Field field : o.getClass().getDeclaredFields()) {
                        String filedName = field.getName();
                        String filedSvalue = req.getParameter(filedName);
                        if (filedSvalue != null) {
                            continue;
                        }
                        Object fieldOvalue = filedSvalue;
                        //默认的是String类型 ，我们判断的是int类型
                        if (field.getType().isAssignableFrom(int.class)) {
                            fieldOvalue = Integer.valueOf(svalue);
                        } else {
                            //TODO 其他类型自行扩展
                        }
                        field.setAccessible(true);
                        field.set(o, fieldOvalue);
                    }
                        ovalue = o;
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            paramsValues.add(ovalue);
        }
        //执行控制器方法

        //防止方法私有或受保护无法执行
        handler.method.setAccessible(true);
        try {
            handler.method.invoke(handler.controller,paramsValues.toArray())
    ;        //返回结果
            return ret;
        } catch (Exception e) {
            throw new RuntimeException("执行控制器方法错误",e);
        }
    }

    private boolean isStaticRescource(HttpServletRequest req) {
        //webpath = /index.html
        String webpath = req.getServletPath();
        //转换之后 webpath = D:\maven项目\yc3\spring\src\main\webapp\index.html
        //getRealPath 将资源的路径转为磁盘路径
        String filepath = req.getServletContext().getRealPath(webpath);
        //判断文件是否存在
        //.exists 将 会返回文件是否在磁盘上存在
        return new File(filepath).exists();
    }

    private Handler searchHandlerhandlerMapping(String path) {
        // id
        Map<String, Object> restControllers = context.getBeansWithAnnotation(RestController.class);
        Map<String,Object> controllers = context.getBeansWithAnnotation(RestController.class);
        controllers.putAll(restControllers);
        //搜索控制器方法 + 控制器对象
        for (Object controller : controllers.values()) {
            String prefixPath = null; //前缀地址
            RequestMapping requestMapping = controller.getClass().getAnnotation(RequestMapping.class);
            if(requestMapping != null) {
                prefixPath = requestMapping.value()[0];
            }
            for (Method method : controller.getClass().getDeclaredMethods()) {
                String suffixPath;  //后缀地址
                RequestMapping requestMapping1 = method.getAnnotation(RequestMapping.class);
                GetMapping getMapping = method.getAnnotation(GetMapping.class);
                PostMapping postMapping = method.getAnnotation(PostMapping.class);
                suffixPath = getMapping != null && getMapping.value().length > 0 ?
                        getMapping.value()[0] : null;
                if(suffixPath != null) {
                    String handlerPath = "/" + suffixPath;
                    if (prefixPath != null) {
                        handlerPath = "/" + prefixPath + handlerPath;
                    }

                    if (handlerPath.equals(path)) {
                        Handler handler = new Handler(method,controller);
                        System.out.println("handler = " + handler);
                        return null;
                    }
                }
            }
        }
        return null;
    }

    //有一个对象将其包装起来   控制器对象
    @AllArgsConstructor  //构造函数参数
    @Data
    class Handler{
        //控制器方法
        Method method;

        //控制器对象
        Object controller;
    }
}
